package com.gitee.wsl.compose.draw.sketch


import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.gitee.wsl.common.ui.draw.DrawCache
import com.gitee.wsl.compose.ui.base.Divider
import com.gitee.wsl.compose.ui.base.Vertical
import com.gitee.wsl.ext.base.currentTimeMillis
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.time.Duration
import kotlin.time.measureTime

/*import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import ui.SketchView*/

/**
 * Builder construct for the K5-compose.
 * Can be called like, main() = k5 {...}
 * All the params passed are applied to a [Window] component
 *
 * @param size The size param for the size of window - [Size]
 * The size is taken in floats in order to perform canvas related operations easily on floats which includes the
 * dimension of window
 * @param title The title of the window.
 * The title is displayed in the windows's native border.
 * @param size The initial size of the window.
 * @param icon The icon for the window displayed on the system taskbar.
 * @param undecorated Removes the native window border if set to true. The default value is false.
 * @param resizable Makes the window resizable if is set to true and unresizable if is set to
 * false. The default value is true.
 * @param focusable Can window receive focus
 * @param alwaysOnTop Should window always be on top of another windows
 * @param onPreviewKeyEvent This callback is invoked when the user interacts with the hardware
 * keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
 * Return true to stop propagation of this event. If you return false, the key event will be
 * sent to this [onPreviewKeyEvent]'s child. If none of the children consume the event,
 * it will be sent back up to the root using the onKeyEvent callback.
 * @param onKeyEvent This callback is invoked when the user interacts with the hardware
 * keyboard. While implementing this callback, return true to stop propagation of this event.
 * If you return false, the key event will be sent to this [onKeyEvent]'s parent.
 */
@Composable
fun k5(
    size: Size = Size(1000f, 1000f),
    title: String = "K5 Compose Playground",
    icon: Painter? = null,
    undecorated: Boolean = false,
    resizable: Boolean = false,
    enabled: Boolean = true,
    focusable: Boolean = true,
    alwaysOnTop: Boolean = false,
    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
    onKeyEvent: (KeyEvent) -> Boolean = { false },
    init: K5.() -> Unit,
) {
    val view = remember {
        K5(
            size,
            title,
            icon,
            undecorated,
            resizable,
            enabled,
            focusable,
            alwaysOnTop,
            onPreviewKeyEvent,
            onKeyEvent
        ).apply(init)
    }
    //view.init()
    val density = LocalDensity.current
    with(view) {
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            if (controls != null) {
                Box {
                    Column(
                        modifier = Modifier
                            .heightIn(10.dp, 200.dp)
                            .background(Color.LightGray)
                            .padding(5.dp)
                    ) {
                        controls!!()
                        Text("step:${stepDelay}", fontSize = 10.sp)
                        Slider(
                            value = stepDelay.toFloat(),
                            onValueChange = { step(it.toInt()) },
                            valueRange = 1f..20f
                        )
                        Row(Modifier.height(35.dp)) {
                            Button(
                                onClick = {
                                    started = !started
                                    if (started) {
                                        start()
                                    }
                                }
                            ) {
                                Text(if (started) "Stop" else "Start", fontSize = 15.sp)
                            }
                            Divider.Vertical()
                            Text( "Cache Draw", fontSize = 15.sp)
                            Checkbox(checked = isDrawCache, onCheckedChange = {isDrawCache=!isDrawCache})
                            Divider.Vertical()
                            Text(
                                " Time:",
                                fontSize = 12.sp
                            )
                            Text(text ="$fps : $elapsed", fontSize = 8.sp)
                        }
                    }
                }
            }
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(1f)
                    .onSizeChanged {
                        with(density) {
                            width = it.width.toDp()
                            height = it.height.toDp()
                        }
                    }
                    .clipToBounds()
            ) {
                SketchView(view)
            }
        }
    }
}

class K5(
    val size: Size,
    val title: String,
    val icon: Painter?,
    val undecorated: Boolean,
    val resizable: Boolean,
    val enabled: Boolean,
    val focusable: Boolean,
    val alwaysOnTop: Boolean,
    val onPreviewKeyEvent: (KeyEvent) -> Boolean,
    val onKeyEvent: (KeyEvent) -> Boolean,
) {

    var stopLoop by mutableStateOf(false)

    var isDrawCache by mutableStateOf(true)

    var width by mutableStateOf(0.dp)
    var height by mutableStateOf(0.dp)

    var started by mutableStateOf(true)
    var paused by mutableStateOf(false)
    var finished by mutableStateOf(false)

    var reader: (drawScope: DrawScope) -> Unit = {}

    var controls: (@Composable () -> Unit)? = null

    var elapsed by mutableStateOf(0L)

    var fps  = Duration.ZERO

    var stepDelay by mutableIntStateOf(0)
    var isStepRun = true
    var stepLoopCount = 0

    private var onUpdateFun: (suspend (Long) -> Unit)? = null

    private var scopeJob :Job? = null

    fun tick(block: suspend (Long) -> Unit) {
        onUpdateFun = block
    }

    fun update(scope: CoroutineScope, deltaTimeNanos: Long) {
        elapsed += deltaTimeNanos
        if (onUpdateFun != null && started /*&& isStepRun()*/) {
            if (scopeJob == null /*|| scopeJob?.isCompleted == true*/) {
                scopeJob = scope.launch { onUpdateFun!!.invoke(deltaTimeNanos) }
                scopeJob!!.invokeOnCompletion {
                    scopeJob = null
                }
            }
        }
    }

    /*fun isStepRun():Boolean{
        if( stepDelay < 2) return true
        return isStepRun
    }*/

    fun step(stepDelay: Int) {
        this.stepDelay = stepDelay
        isStepRun = true
        stepLoopCount = 0
    }

    fun noDrawCache() {
        isDrawCache = false
    }

    fun stepLoop() {
        if (stepDelay > 1) {
            stepLoopCount++
            if (stepLoopCount == stepDelay) {
                isStepRun = true
                stepLoopCount = 0
            } else if (isStepRun)
                isStepRun = false
        }
    }

    /**
     * Call method to stop the looping of canvas
     * You can also call it to freeze the time frame for a canvas
     */
    fun noLoop() {
        this.stopLoop = true
    }

    fun start() {
        started = true
        finished = false
        paused = false
    }

    /**
     * Use this property to get the actual [k5] Playground size in Floats. Subtracting the 56f - which is the toolbar height of the window.
     * When the size of the window is set with `size` param in [k5] builder, it's applied to window and when
     * the canvas is rendered in the window with [Modifier.fillMaxSize] it takes whole window except the toolbar.
     *
     * TODO: Fix the dimensions for a given k5 playground considering density
     */
    val dimensFloat = Size(size.width, size.height - 56f)

    /**
     * Use this property to get the actual [k5] Playground size in Ints
     */
    val dimensInt = IntSize(size.width.toInt(), size.height.toInt() - 56)

    /**
     * Shows the canvas window and renders it for each frame repetitively.
     * Internally, this starts the Jetpack Compose Window and renders the [sketch] requested by user into the Jetpack Compose
     * [Canvas] Composable. The size of the [Canvas] will be same as the [size] passed in [k5] method by default.
     * One can change the canvas size and window size with the help of modifiers.
     * In order to keep the animation running (rendering canvas continuously), it requests to run the frame of animation in nanos.
     * All the [modifier] will be applied to the [Canvas].
     *
     * @param modifier Jetpack compose [Modifier]
     * @param sketch drawScope - Compose canvas drawscope
     */
    fun onDraw(
        //modifier: Modifier = Modifier,
        //controls: (@Composable () -> Unit)? = null,
        sketch: (drawScope: DrawScope) -> Unit,
    ) {
        reader = sketch
    }

    fun controls(controls: @Composable () -> Unit){
        this.controls = controls
    }

    /**
     * Shows canvas window as well as controls view side by side.
     * Internally, this starts the Jetpack Compose Window and renders the [sketch] requested by user into the Jetpack Compose
     * [Canvas] Composable. The size of the [Canvas] will be same as the [size] passed in [k5] method by default.
     * One can change the canvas size and window size with the help of modifiers.
     * In order to keep the animation running (rendering canvas continuously), it requests to run the frame of animation in nanos.
     *
     * Also, you can add your controls like sliders, checkboxes, radio-buttons, pickers etc as a control to change/provide
     * inputs to your sketch. This is just simple [Composable] function, so you can add all the [Composable] elements you want.
     * The controls are shown on the right side in the window. You can use Compose States to store/change your input and use it in your sketch.
     */
    fun onDrawWithControls(
        //modifier: Modifier = Modifier,
        controls: (@Composable () -> Unit)? = null,
        sketch: (drawScope: DrawScope) -> Unit,
    ) {
        this.controls = controls
        reader = sketch
    }

    fun upFps(time: Duration) {
       fps = time
    }

}


/**
 * Simple canvas view to render the sketch
 */
@Composable
internal fun SketchView(k5: K5) {
    // dt = change in time
    val dt = remember { mutableStateOf(0f) }
    // TODO : Show elapsed time and frames per second on toolbar of window
    var startTime = remember { mutableStateOf(0L) }
    val previousTime = remember { mutableStateOf(currentTimeMillis) }
    val cache = remember { DrawCache() }
    var tinkJob:Job? = null
    val size = remember { mutableStateOf(IntSize.Zero) }
    val scope = rememberCoroutineScope()
    val density = LocalDensity.current

    Canvas(modifier = Modifier
        .fillMaxSize()
        .background(Color.Black)
        .onSizeChanged { size.value = it }
    ) {
        val stepFrame = dt.value

        val time = measureTime {
            if (k5.started && !k5.stopLoop && k5.isDrawCache && stepFrame != 0f && k5.isStepRun) {
                if (tinkJob == null) {
                    tinkJob = scope.launch {
                        cache.drawCachedImage(size.value, density) {
                            k5.reader(this)
                        }
                    }
                    Timber.d("draw intel cache job created")
                    tinkJob!!.invokeOnCompletion {
                        tinkJob = null
                    }
                }
            }

            if (k5.isDrawCache && stepFrame != 0f) {
                /*if (tinkJob?.isCompleted == true) {
                    cache.drawInto(this)
                    tinkJob = null
                    Timber.d("draw intel cache isCompleted step:${k5.isStepRun}")
                } else*/ if (cache.isReady) {
                    cache.drawInto(this)
                    Timber.d("draw intel cache read step:${k5.isStepRun}")
                } else {
                    k5.reader(this)
                    Timber.d("draw intel cache real step:${k5.isStepRun}")
                }
            } else {
                k5.reader(this)
                Timber.d("draw intel  real step:${k5.isStepRun}")
            }


            /*val time=K.measureTime {
              cache.drawCachedImage(this.size.toIntSize(), Density(this.density)){
                  sketch (this)
              }
              cache.drawInto(this)
            }*/

            /* val time=K.measureTime {
                 sketch (this)
            }*/


            //Timber.d("draw cache time = $time")
            //sketch (this)
        }

        k5.upFps(time)

    }


    if (!k5.stopLoop) {
        Timber.d("draw intel animation loop")
        //requestAnimationFrame(dt, previousTime)
        LaunchedEffect(Unit) {
            while (true) {
                withInfiniteAnimationFrameMillis {
                    k5.stepLoop()
                }
                if (k5.started && !k5.paused && !k5.finished && k5.isStepRun) {
                    withInfiniteAnimationFrameMillis {
                        val delay = it - previousTime.value
                        dt.value = (delay / 1E7).toFloat()
                        previousTime.value = it
                        k5.update(scope, delay.coerceAtLeast(0))
                    }
                }
            }
        }
    }

}